﻿using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;

namespace Client
{
    public sealed class MirMap
    {
        [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
        private sealed class MapHeader
        {
            public static int MySize { get { return Marshal.SizeOf(typeof(MapHeader)); } }

            public ushort Width;
            public ushort Height;
            private byte TitleLength;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
            public string Title;
            public DateTime UpdateDate;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 19)]
            public byte[] Reserved = new byte[19];
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
        private sealed class MapHeaderAntiHack
        {
            public static int MySize { get { return Marshal.SizeOf(typeof(MapHeaderAntiHack)); } }

            private byte TitleLength;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
            public string Title;
            public ushort Width;
            public ushort CheckKey; //Ã¼Å©°ª=43576
            public ushort Height;
            public DateTime UpdateDate;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 19)]
            public byte[] Reserved = new byte[19];
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct MapInfo
        {
            public static int MySize { get { return Marshal.SizeOf(typeof(MapInfo)); } }

            public ushort BigTileImage;
            public ushort SmallTileImage;
            public ushort ObjectImage;
            public byte DoorIndex;     //$80 (¹®Â¦), ¹®ÀÇ ½Äº° ÀÎµ¦½º
            public byte DoorOffset;    //´ÝÈù ¹®ÀÇ ±×¸²ÀÇ »ó´ë À§Ä¡, $80 (¿­¸²/´ÝÈû(±âº»))
            public byte AniFrame;      //$80(Draw Alpha) +  ÇÁ·¡ÀÓ ¼ö
            public byte AniTick;
            public byte ObjectFile;
            public byte Light;        //0..1..4 ±¤¿ø È¿°ú
        }

        public static readonly MirMap Me = new MirMap();

        public const string MAPDIR = "./Map/";
        public const int MAXX = 40;
        public const int MAXY = 40;

        public const int LOGICALMAPUNIT = 40;
        public const int UNITX = 48;
        public const int UNITY = 32;
        public const int HALFX = 24;
        public const int HALFY = 16;

        public static string[] NewMapArr = 
        {
            "LABY01",
            "LABY02",
            "LABY03",
            "LABY04",
            "SNAKE"
        };

        public string MapBase;
        public MapInfo[,] MArr;
        public Rectangle ClientRect;
        public Rectangle OldClientRect;
        public int BlockLeft, BlockTop; //Å¸ÀÏ ÁÂÇ¥·Î ¿ÞÂÊ, ²À´ë±â ÁÂÇ¥
        public int OldLeft, OldTop;
        public string OldMap;
        public int CurUnitX, CurUnitY;
        public string CurrentMap;
        public bool Segmented;
        public int SegXCount, SegYCount;


        private MirMap()
            : base()
        {
            MArr = new MapInfo[(MAXX * 3) + 1, (MAXY * 3) + 1];
            ClientRect = Rectangle.Empty;
            OldClientRect = Rectangle.Empty;
            MapBase = MAPDIR;
            CurrentMap = string.Empty;
            Segmented = false;
            SegXCount = 0;
            SegYCount = 0;
            CurUnitX = -1;
            CurUnitY = -1;
            BlockLeft = -1;
            BlockTop = -1;
            OldMap = string.Empty;
        }

        private void ConvertMapInfo(IntPtr Ptr, ref MapInfo mi, int ofs)
        {
            IntPtr offsetPtr = (IntPtr)((long)Ptr + (long)ofs);
            mi = (MapInfo)Marshal.PtrToStructure(offsetPtr, typeof(MapInfo));
        }

        private void ReadMapInfo(BinaryReader reader, ref MapInfo mi)
        {
            mi.BigTileImage = reader.ReadUInt16();
            mi.SmallTileImage = reader.ReadUInt16();
            mi.ObjectImage = reader.ReadUInt16();
            mi.DoorIndex = reader.ReadByte();
            mi.DoorOffset = reader.ReadByte();
            mi.AniFrame = reader.ReadByte();
            mi.AniTick = reader.ReadByte();
            mi.ObjectFile = reader.ReadByte();
            mi.Light = reader.ReadByte();
        }

        private bool LoadMapInfo(string mapfile, ref int Width, ref int Height)
        {
            string flname = MapBase + mapfile;

            if (File.Exists(flname) == false)
                return false;

            using (BinaryReader reader = new BinaryReader(File.Open(flname, FileMode.Open, FileAccess.Read, FileShare.None)))
            {
                Width = (int)reader.ReadUInt16();
                Height = (int)reader.ReadUInt16();
            }

            return true;
        }

        private void UpdateMapSegment(int cx, int cy)
        {
            // Not Used
        }

        private void UpdateMap(int cx, int cy)
        {
            int i, j, ofs, aline, lx, rx, ty, by;
            ushort width, height, ckey;
            string flname, Tempstr;
            bool newformat = false;

            Array.Clear(MArr, 0, MArr.Length);
            //for (i = 0; i < (MAXX * 3) + 1; i++)
            //    for (j = 0; j < (MAXY * 3) + 1; j++)
            //        MArr[i, j] = new TMapInfo();

            flname = MapBase + CurrentMap + ".map";
            Tempstr = CurrentMap.ToUpper();
            if (File.Exists(flname) == false)
                return;

            foreach (string s in NewMapArr)
            {
                if (s == Tempstr)
                    newformat = true;
            }

            if (newformat)
            {
                using (BinaryReader mreader = new BinaryReader(File.Open(flname, FileMode.Open, FileAccess.Read, FileShare.None)))
                {
                    mreader.BaseStream.Position = 32;
                    width = mreader.ReadUInt16();
                    ckey = mreader.ReadUInt16();
                    height = mreader.ReadUInt16();

                    lx = (cx - 1) * LOGICALMAPUNIT;
                    rx = (cx + 2) * LOGICALMAPUNIT;    //rx
                    ty = (cy - 1) * LOGICALMAPUNIT;
                    by = (cy + 2) * LOGICALMAPUNIT;
                    if (lx < 0)
                        lx = 0;
                    if (ty < 0)
                        ty = 0;
                    if (by >= height)
                        by = height;
                    aline = MapInfo.MySize * height;
                    for (i = lx; i < rx; i++)
                    {
                        for (j = ty; j <= by; j++)
                        {
                            ofs = MapHeaderAntiHack.MySize + (aline * i) + (MapInfo.MySize * j);
                            mreader.BaseStream.Seek(ofs, SeekOrigin.Begin);
                            ReadMapInfo(mreader, ref MArr[i - lx, j - ty]);

                            MArr[i - lx, j - ty].BigTileImage ^= ckey;
                            MArr[i - lx, j - ty].SmallTileImage ^= ckey;
                            MArr[i - lx, j - ty].ObjectImage ^= ckey;
                        }
                    }
                }

                /*MapHeaderAntiHack header = new MapHeaderAntiHack();

                byte[] byteData = File.ReadAllBytes(flname);
                IntPtr ptr = Marshal.AllocHGlobal(byteData.Length);
                Marshal.Copy(byteData, 0, ptr, byteData.Length);
                Marshal.PtrToStructure(ptr, header);
                header.Width ^= header.CheckKey;
                header.Height ^= header.CheckKey;

                lx = (cx - 1) * LOGICALMAPUNIT;
                rx = (cx + 2) * LOGICALMAPUNIT;    //rx
                ty = (cy - 1) * LOGICALMAPUNIT;
                by = (cy + 2) * LOGICALMAPUNIT;
                if (lx < 0)
                    lx = 0;
                if (ty < 0)
                    ty = 0;
                if (by >= header.Height)
                    by = header.Height;
                aline = MapInfo.MySize * header.Height;
                for (i = lx; i < rx; i++)
                {
                    for (j = ty; j <= by; j++)
                    {
                        ofs = MapHeaderAntiHack.MySize + (aline * i) + (MapInfo.MySize * j);
                        ConvertMapInfo(ptr, ref MArr[i - lx, j - ty], ofs);

                        MArr[i - lx, j - ty].BigTileImage ^= header.CheckKey;
                        MArr[i - lx, j - ty].SmallTileImage ^= header.CheckKey;
                        MArr[i - lx, j - ty].ObjectImage ^= header.CheckKey;
                    }
                }
                Marshal.FreeHGlobal(ptr);*/
            }
            else
            {
                using (BinaryReader mreader = new BinaryReader(File.Open(flname, FileMode.Open, FileAccess.Read, FileShare.None)))
                {
                    width = mreader.ReadUInt16();
                    height = mreader.ReadUInt16();

                    lx = (cx - 1) * LOGICALMAPUNIT;
                    rx = (cx + 2) * LOGICALMAPUNIT;    //rx
                    ty = (cy - 1) * LOGICALMAPUNIT;
                    by = (cy + 2) * LOGICALMAPUNIT;
                    if (lx < 0)
                        lx = 0;
                    if (ty < 0)
                        ty = 0;
                    if (by >= height)
                        by = height;
                    aline = MapInfo.MySize * height;
                    for (i = lx; i < rx; i++)
                    {
                        for (j = ty; j <= by; j++)
                        {
                            ofs = MapHeader.MySize + (aline * i) + (MapInfo.MySize * j);
                            mreader.BaseStream.Seek(ofs, SeekOrigin.Begin);
                            ReadMapInfo(mreader, ref MArr[i - lx, j - ty]);
                        }
                    }
                }
                
                
                /*MapHeader header = new MapHeader();

                byte[] byteData = File.ReadAllBytes(flname);
                IntPtr ptr = Marshal.AllocHGlobal(byteData.Length);
                Marshal.Copy(byteData, 0, ptr, byteData.Length);
                Marshal.PtrToStructure(ptr, header);

                lx = (cx - 1) * LOGICALMAPUNIT;
                rx = (cx + 2) * LOGICALMAPUNIT;    //rx
                ty = (cy - 1) * LOGICALMAPUNIT;
                by = (cy + 2) * LOGICALMAPUNIT;
                if (lx < 0)
                    lx = 0;
                if (ty < 0)
                    ty = 0;
                if (by >= header.Height)
                    by = header.Height;
                aline = MapInfo.MySize * header.Height;
                for (i = lx; i < rx; i++)
                {
                    for (j = ty; j <= by; j++)
                    {
                        ofs = MapHeader.MySize + (aline * i) + (MapInfo.MySize * j);
                        ConvertMapInfo(ptr, ref MArr[i - lx, j - ty], ofs);
                    }
                }
                Marshal.FreeHGlobal(ptr);*/
            }
        }




        public void UpdateMapSquare(int cx, int cy)
        {
            if (cx != CurUnitX || cy != CurUnitY)
            {
                if (Segmented)
                    UpdateMapSegment(cx, cy);
                else
                    UpdateMap(cx, cy);

                CurUnitX = cx;
                CurUnitY = cy;
            }
        }

        private void Unmark(int cx, int cy, int xx, int yy)
        {
            int ax, ay;

            if ((cx == xx / LOGICALMAPUNIT) && (cy == yy / LOGICALMAPUNIT))
            {
                ax = xx - BlockLeft;
                ay = yy - BlockTop;
                MArr[ax, ay].ObjectImage = (ushort)(MArr[ax, ay].ObjectImage & 0x7FFF);
                MArr[ax, ay].BigTileImage = (ushort)(MArr[ax, ay].BigTileImage & 0x7FFF);
            }
        }

        public void UpdateMapPos(int mx, int my)
        {
            int cx = mx / LOGICALMAPUNIT;
            int cy = my / LOGICALMAPUNIT;
            BlockLeft = Math.Max(0, (cx - 1) * LOGICALMAPUNIT);
            BlockTop = Math.Max(0, (cy - 1) * LOGICALMAPUNIT);

            UpdateMapSquare(cx, cy);

            if ((OldLeft != BlockLeft) || (OldTop != BlockTop) || (OldMap != CurrentMap))
            {
                //3¹ø¸Ê ¼ºº®ÀÚ¸® ¹ö±× º¸Á¤ (2001-7-3)
                if (CurrentMap == "3")
                {
                    Unmark(cx, cy, 624, 278);
                    Unmark(cx, cy, 627, 278);
                    Unmark(cx, cy, 634, 271);

                    Unmark(cx, cy, 564, 287);
                    Unmark(cx, cy, 564, 286);
                    Unmark(cx, cy, 661, 277);
                    Unmark(cx, cy, 578, 296);
                }
            }
            OldLeft = BlockLeft;
            OldTop = BlockTop;
        }

        public void ReadyReload()
        {
            CurUnitX = -1;
            CurUnitY = -1;
        }

        public void LoadMap(string mapname, int mx, int my)
        {
            CurUnitX = -1;
            CurUnitY = -1;
            CurrentMap = mapname;
            Segmented = false; //Segmented µÇ¾î ÀÖ´ÂÁö °Ë»çÇÑ´Ù.
            UpdateMapPos(mx, my);
            OldMap = CurrentMap;
        }

        public void MarkCanWalk(int mx, int my, bool bowalk)
        {
            int cx = mx - BlockLeft;
            int cy = my - BlockTop;
            if (cx < 0 || cy < 0)
                return;

            if (bowalk) //°ÉÀ» ¼ö ÀÖÀ½
                MArr[cx, cy].ObjectImage = (ushort)(MArr[cx, cy].ObjectImage & 0x7FFF);
            else //¸·ÇûÀ½
                MArr[cx, cy].ObjectImage = (ushort)(MArr[cx, cy].ObjectImage | 0x8000);  //¸ø¿òÁ÷ÀÌ°Ô ÇÑ´Ù.
        }

        public bool CanMove(int mx, int my)
        {
            int cx = mx - BlockLeft;
            int cy = my - BlockTop;
            if (cx < 0 || cy < 0)
                return false;

            bool b = ((MArr[cx, cy].BigTileImage & 0x8000) + (MArr[cx, cy].ObjectImage & 0x8000)) == 0;

            if (b) //¹®°Ë»ç
            {
                if ((MArr[cx, cy].DoorIndex & 0x80) > 0)  //¹®Â¦ÀÌ ÀÖÀ½
                {
                    if ((MArr[cx, cy].DoorOffset & 0x80) == 0)
                        b = false; //¹®ÀÌ ¾È ¿­·ÈÀ½.
                }
            }

            return b;
        }

        public bool CanFly(int mx, int my)
        {
            int cx = mx - BlockLeft;
            int cy = my - BlockTop;
            if (cx < 0 || cy < 0)
                return false;

            bool b = (MArr[cx, cy].ObjectImage & 0x8000) == 0;

            if (b) //¹®°Ë»ç
            {
                if ((MArr[cx, cy].DoorIndex & 0x80) > 0)  //¹®Â¦ÀÌ ÀÖÀ½
                {
                    if ((MArr[cx, cy].DoorOffset & 0x80) == 0)
                        b = false; //¹®ÀÌ ¾È ¿­·ÈÀ½.
                }
            }

            return b;
        }

        public int GetDoor(int mx, int my)
        {
            int cx = mx - BlockLeft;
            int cy = my - BlockTop;

            if ((MArr[cx, cy].DoorIndex & 0x80) > 0)
                return MArr[cx, cy].DoorIndex & 0x7F;

            return 0;
        }

        public bool IsDoorOpen(int mx, int my)
        {
            int cx = mx - BlockLeft;
            int cy = my - BlockTop;

            if ((MArr[cx, cy].DoorIndex & 0x80) > 0)
                return ((MArr[cx, cy].DoorOffset & 0x80) != 0);

            return false;
        }

        public bool OpenDoor(int mx, int my)
        {
            int i, j, idx;

            int cx = mx - BlockLeft;
            int cy = my - BlockTop;
            if (cx < 0 || cy < 0)
                return false;

            bool b = (MArr[cx, cy].DoorIndex & 0x80) > 0;

            if (b)
            {
                idx = MArr[cx, cy].DoorIndex & 0x7F;
                for (i = cx - 10; i <= cx + 10; i++)
                {
                    for (j = cy - 10; j <= cy + 10; j++)
                    {
                        if (i > 0 && j > 0)
                        {
                            if ((MArr[i, j].DoorIndex & 0x7F) == idx)
                                MArr[i, j].DoorOffset = (byte)(MArr[i, j].DoorOffset | 0x80);

                            b = true;
                        }
                    }
                }
            }

            return b;
        }

        public bool CloseDoor(int mx, int my)
        {
            int i, j, idx;

            int cx = mx - BlockLeft;
            int cy = my - BlockTop;
            if (cx < 0 || cy < 0)
                return false;

            bool b = (MArr[cx, cy].DoorIndex & 0x80) > 0;

            if (b)
            {
                idx = MArr[cx, cy].DoorIndex & 0x7F;
                for (i = cx - 8; i <= cx + 10; i++)
                {
                    for (j = cy - 8; j <= cy + 10; j++)
                    {
                        if (i > 0 && j > 0)
                        {
                            if ((MArr[i, j].DoorIndex & 0x7F) == idx)
                                MArr[i, j].DoorOffset = (byte)(MArr[i, j].DoorOffset & 0x7F);

                            b = true;
                        }
                    }
                }
            }

            return b;
        }

    }
}